using System;
using System.Collections.Generic;
using Microsoft.WindowsCE.Forms;
using System.ComponentModel;
using Microsoft.Win32;
using System.Reflection;

namespace ManagedService
{
    /// <summary>
    /// Enumeration for supported Service states
    /// </summary>
    public enum ServiceState
    {
        /// <summary>
        /// Service is starting
        /// </summary>
        Starting,
        /// <summary>
        /// Service is running
        /// </summary>
        Running,
        /// <summary>
        /// Service is going to suspend
        /// </summary>
        Suspending,
        /// <summary>
        /// Service is suspended
        /// </summary>
        Suspended,
        /// <summary>
        /// Service is going to resume
        /// </summary>
        Resuming,
        /// <summary>
        /// Service is going to exit
        /// </summary>
        Exiting,
        /// <summary>
        /// Service has exited
        /// </summary>
        Exited
    }

    /// <summary>
    /// Enumeration for supported ServiceStart types
    /// </summary>
    public enum ServiceStartType
    {
        /// <summary>
        /// Service will be started automatically on Startup. This requires, that ManagedServicesLauncher is installed and that Service is registered
        /// </summary>
        Automatic,
        /// <summary>
        /// Service will  not be started autmatically.
        /// </summary>
        Manual
    }

    /// <summary>
    /// This Abstract class provides functionality to run application on background and listen to windows messages.
    /// A derived class is immune to smart minimizing on Windows Mobile.
    /// This class can't be instatiated.
    /// </summary>
    public abstract class ServiceApplication : ManagedService.IServiceApplication
    {
        #region Fields

        /// <summary>
        /// USER messages starts from 0x400
        /// </summary>
        public const int WM_USER = 0x400;
        /// <summary>
        /// START message
        /// </summary>
        public const int UM_START_SERVICE = WM_USER + 5001;
        /// <summary>
        /// SUSPEND message
        /// </summary>
        public const int UM_SUSPEND_SERVICE = WM_USER + 5002;
        /// <summary>
        /// RESUME message
        /// </summary>
        public const int UM_RESUME_SERVICE = WM_USER + 5003;
        /// <summary>
        /// EXIT message
        /// </summary>
        public const int UM_QUIT_SERVICE = WM_USER + 5004;

        /// <summary>
        /// Used for exiting the MainLoop
        /// </summary>
        private bool m_exitMainLoop = false;

        /// <summary>
        /// The Name of the Service
        /// </summary>
        private string m_name = "";

        /// <summary>
        /// The current State for the Service
        /// </summary>
        private ServiceState m_serviceState = ServiceState.Exited;

        /// <summary>
        /// The unique identifier for the Service
        /// </summary>
        private Guid m_serviceGuid;

        /// <summary>
        /// The StartType of the current Service
        /// The default setting is ServiceStartType.Manual
        /// </summary>
        private ServiceStartType m_startType = ServiceStartType.Manual;
      
        /// <summary>
        /// Message window for the ServiceApplication
        /// </summary>
        private static ServiceMessageWindow m_messageWindow;

        #endregion

        #region Events and delegates

        /// <summary>
        /// Occurs when a registered Windows Message is received.
        /// </summary>
        /// <param name="message">Received Message</param>
        public delegate void RegisteredMessageReceivedEventHandler(ref Microsoft.WindowsCE.Forms.Message message);
        /// <summary>
        /// Raises the RegisteredMessageReceived event.
        /// </summary>
        public event RegisteredMessageReceivedEventHandler OnRegisteredMessage;

        /// <summary>
        /// Occurs when ServiceState changes.
        /// </summary>
        /// <param name="serviceStateEventArgs">Contains information on the ServiceState</param>
        public delegate void ServiceStateChangedEventHandler(ServiceStateEventArgs serviceStateEventArgs);
        /// <summary>
        /// Raises the ServiceStateChanged event.
        /// </summary>
        public event ServiceStateChangedEventHandler OnServiceStateChanged;

        /// <summary>
        /// Occurs when Service is exiting.
        /// </summary>
        /// <param name="cancelEventArgs">Allows cancellation of exiting the Service</param>
        public delegate void ServiceExitingEventHandler(CancelEventArgs cancelEventArgs);
        /// <summary>
        /// Raises the ServiceExiting event.
        /// </summary>
        public event ServiceExitingEventHandler OnServiceExiting;

        #endregion

        #region Public methods

        /// <summary>
        /// Initialize ServiceApplication.
        /// </summary>
        protected ServiceApplication()
        {
            if (IsServiceRegistered())
            {
                RegistryKey mainkey  = Registry.LocalMachine.OpenSubKey("Software\\ManagedServices\\" + m_serviceGuid.ToString("N"), true);

                if (mainkey != null)
                {
                    switch ((int)mainkey.GetValue("StartType"))
	                {
                        case 1:
                            m_startType = ServiceStartType.Automatic;
                            break;
                        case 0:
                            m_startType = ServiceStartType.Manual;
                            break;
		                default:
                            break;
	                } 
                }
            }
            m_messageWindow = new ServiceMessageWindow();
            RegisterMessage(UM_START_SERVICE);
            RegisterMessage(UM_SUSPEND_SERVICE);
            RegisterMessage(UM_RESUME_SERVICE);
            RegisterMessage(UM_QUIT_SERVICE);

            m_messageWindow.OnRegisteredMessage +=new ServiceMessageWindow.RegisteredMessageEventHandler(_messageWindow_OnRegisteredMessage);
        }

        /// <summary>
        /// Begins application message loop. This keeps the Main-Thread running and prevents it from closing.
        /// It mainly calls continously "System.Windows.Forms.Application.DoEvents();" for fetching WIndows Messages
        /// </summary>
        private void Run()
        {
            ServiceState = ServiceState.Running;
            while (!m_exitMainLoop)
            {
                if (m_messageWindow.Text == "")
                {
                    break;
                }
                System.Windows.Forms.Application.DoEvents();
                // Adding Thread.Sleep for lower CPU usage
                // Fix for issue #1171
                System.Threading.Thread.Sleep(200);
            }
            ServiceState = ServiceState.Exited;
        }

        /// <summary>
        /// Is executed during startup of the service
        /// </summary>
        public virtual void Start()
        {
            ServiceState = ServiceState.Starting;
            Run();
        }

        /// <summary>
        /// Informs Service to suspend
        /// </summary>
        public virtual void Suspend()
        {
            ServiceState = ServiceState.Suspending;
            ServiceState = ServiceState.Suspended;
        }

        /// <summary>
        /// Informs Service to resume
        /// </summary>
        public virtual void Resume()
        {
            ServiceState = ServiceState.Resuming;
            ServiceState = ServiceState.Running;
        }
        /// <summary>
        /// Informs Service to quit. It fires the OnServiceExiting-Event, which uses the CancelEventsArgs as a parameter. By that an exit can be prevented by code.
        /// </summary>
        public virtual void Quit()
        {
            ServiceState = ServiceState.Exiting;
            CancelEventArgs eventArgs = new CancelEventArgs(false);
            if (OnServiceExiting != null)
                OnServiceExiting(eventArgs);
            if(eventArgs.Cancel == true)
                ServiceState = ServiceState.Running;
            else
            {
                m_exitMainLoop = true;
            }
        }

        /// <summary>
        /// Registers a Service within the Registry.
        /// The root will be created under HKLM\Software\ManagedServices, if not already created.
        /// Each registered service will have a subkey, which is the ServiceGuid.
        /// </summary>
        public void RegisterService()
        {
            RegistryKey mainkey = Registry.LocalMachine.CreateSubKey("Software\\ManagedServices");
            if (mainkey == null)
            {
                return;
            }
            mainkey = mainkey.CreateSubKey(m_serviceGuid.ToString("N"));
            mainkey.SetValue("ServiceName", m_name, RegistryValueKind.String);
            switch (m_startType)
	        {
		        case ServiceStartType.Automatic:
                    mainkey.SetValue("StartType",1,RegistryValueKind.DWord);
                 break;
                case ServiceStartType.Manual:
                    mainkey.SetValue("StartType",0,RegistryValueKind.DWord);
                 break;
                default:
                 break;
            }
            mainkey.SetValue("ServiceState", m_serviceState.ToString());
            mainkey.SetValue("Executable", Assembly.GetCallingAssembly().GetName().CodeBase);
            mainkey.Flush();
            mainkey.Close();            
        }

        /// <summary>
        /// Unregisters a Service from the Registry, if previously registered.
        /// </summary>
        public void UnRegisterService()
        {
            RegistryKey mainkey = Registry.LocalMachine.OpenSubKey("Software\\ManagedServices", true);
            if (mainkey == null)
            {
                return;
            }
            mainkey.DeleteSubKey(m_serviceGuid.ToString("N"),false);
            mainkey.Flush();
            mainkey.Close();
        }

        /// <summary>
        /// Checks, if a Service has already been registered
        /// </summary>
        /// <returns>true, if a Service is already registered</returns>
        public bool IsServiceRegistered()
        {
            RegistryKey mainkey = Registry.LocalMachine.OpenSubKey("Software\\ManagedServices", true);
            if (mainkey == null)
            {
                return false;
            }
            foreach (string keyname in mainkey.GetSubKeyNames())
            {
                if (keyname.Equals(m_serviceGuid.ToString("N")))
                {
                    return true;
                }
            }
            return false;
        }

        /// <summary>
        /// Changes the StartType within the Registry
        /// </summary>
        private void SetStartType()
        {
            RegistryKey mainkey=null;
            if (IsServiceRegistered())
            {
                mainkey = Registry.LocalMachine.OpenSubKey("Software\\ManagedServices\\" + m_serviceGuid.ToString("N"), true);
                
            }
            if (mainkey == null)
            {
                return;
            }
            switch (m_startType)
            {
                case ServiceStartType.Automatic:
                    mainkey.SetValue("StartType", 1, RegistryValueKind.DWord);
                    break;
                case ServiceStartType.Manual:
                    mainkey.SetValue("StartType", 0, RegistryValueKind.DWord);
                    break;
                default:
                    break;
            }
            mainkey.Flush();
            mainkey.Close();
        }

        /// <summary>
        /// Changes the ServiceState within the Registry
        /// </summary>
        private void SetServiceState()
        {
            RegistryKey mainkey = null;
            if (IsServiceRegistered())
            {
                mainkey = Registry.LocalMachine.OpenSubKey("Software\\ManagedServices\\" + m_serviceGuid.ToString("N"), true);

            }
            if (mainkey == null)
            {
                return;
            }
            mainkey.SetValue("ServiceState", m_serviceState.ToString());
            mainkey.Flush();
            mainkey.Close();
        }

        /// <summary>
        /// Register windows message
        /// </summary>
        /// <param name="messageToRegister">Message to be registered</param>
        /// <returns>False if message is already registered</returns>
        public bool RegisterMessage(int messageToRegister)
        {
            if(!IsMessageRegistered(messageToRegister))
                return m_messageWindow.RegisterMessage(messageToRegister);
            else
            {
                return false;
            }
        }

        /// <summary>
        /// Checks, if a specified message is registered
        /// </summary>
        /// <param name="messageToCheck">Message to be checked</param> 
        /// <returns>True, if message is already registered</returns>
        public bool IsMessageRegistered(int messageToCheck)
        {
            // Messages below WM_User + 5000 aren't allowed to be registered
            // Fix for issue # 1095
            if (messageToCheck < (WM_USER+5000))
            {
                return false;
            }
            return m_messageWindow.IsMessageRegistered(messageToCheck);
        }

        /// <summary>
        /// Unregister windows message
        /// </summary>
        /// <param name="messageToUnregister">Mesage to be unregistered</param>
        /// <returns>True, if message was unregistered successfully</returns>
        public bool UnregisterMessage(int messageToUnregister)
        {
            if (IsMessageRegistered(messageToUnregister))
            {
                if (messageToUnregister == UM_START_SERVICE || messageToUnregister == UM_SUSPEND_SERVICE || messageToUnregister == UM_RESUME_SERVICE)
                    return false;
                else return m_messageWindow.IsMessageRegistered(messageToUnregister);
            }
            else
            {
                return false;
            }
        }

        #endregion

        #region Private methods

        /// <summary>
        /// Event handler for registered messages forwarding the event 
        /// </summary>
        private void _messageWindow_OnRegisteredMessage(ref Microsoft.WindowsCE.Forms.Message message)
        {
            switch (message.Msg)
            {
                case UM_START_SERVICE:
                    if (ServiceState!= ServiceState.Starting || ServiceState!= ServiceState.Running)
                    {
                        Start();                        
                    }
                    break;

                case UM_QUIT_SERVICE:
                    if (ServiceState == ServiceState.Running || ServiceState == ServiceState.Suspended)
                    {
                        Quit();
                    }
                    break;
                case UM_SUSPEND_SERVICE:
                    Suspend();
                    break;
                case UM_RESUME_SERVICE:
                    Resume();
                    break;
            }
            if (OnRegisteredMessage != null)
                    OnRegisteredMessage(ref message);
        }

        #endregion

        #region Public properties

        /// <summary>
        /// Gets collection of registered windows messages
        /// </summary>
        public IEnumerable<int> RegisteredMessages
        {
            get
            {
                foreach (int msg in m_messageWindow.RegisteredMessages)
                    yield return msg;
            }
        }

        /// <summary>
        /// Gets or sets name of the ServiceApplication
        /// </summary>
        public string ServiceName
        {
            get 
            {

                return m_name; 
            }
            set { m_name = value; }
        }

        /// <summary>
        /// Gets the current state of the running Service
        /// </summary>
        public ServiceState ServiceState
        {
            get { return m_serviceState; }
            protected set
            {
                m_serviceState = value;
                SetServiceState();
                if (OnServiceStateChanged != null)
                {
                    ServiceStateEventArgs serviceStateEventArgs = new ServiceStateEventArgs(m_serviceState);
                    OnServiceStateChanged(serviceStateEventArgs);
                }
            }
        }

        /// <summary>
        /// Gets or sets the start type for the current service.
        /// If set to Automatic and ManagedServiceLauncher is installed, services get started by ManagedServiceLauncher during system startup
        /// </summary>
        public ServiceStartType ServiceStartType
        {
            get { return m_startType; }
            set { m_startType = value;
            SetStartType();
            }
        }

        /// <summary>
        /// Gets the ServiceGuid for the current service.
        /// This GUID is the main key for a service and MUST be unique
        /// </summary>
        public Guid ServiceGuid
        {
            get
            {

                return m_serviceGuid;
            }
            protected set
            {
                m_serviceGuid = value;
                m_messageWindow.Text = m_serviceGuid.ToString("N");
            }
        }

        #endregion

    }
}
